/*
 *  canvas.cpp
 *  krryn
 *
 *  Created by Jasper Bekkers on 9/4/07.
 *  Copyright 2007 Jasper Bekkers. All rights reserved.
 *
 */

#include "canvas.h"
#include "algebra.h"
#include "exceptions.h"
#include "image_file.h"
#include <algorithm>
#include <windows.h>
using namespace krryn::core;
using namespace krryn::view;
using namespace krryn::math;

namespace{
	int next_power_of_two(int a_Number){
		if(a_Number & (a_Number - 1)){
			return 1 << (static_cast<int>(log(static_cast<float>(a_Number), 2.f)) + 1);
		}else{
			return a_Number;
		}
	}
}

canvas::canvas(const std::string &a_Filename){
	canvas *l_Canvas = image_file_manager::get_instance()->load(a_Filename);

	if(l_Canvas){
		m_Pitch = l_Canvas->m_Pitch;
		m_Width = l_Canvas->m_Width;
		m_Height = l_Canvas->m_Height;
		m_Locked = l_Canvas->m_Locked;
		m_Pixels = l_Canvas->m_Pixels;
		l_Canvas->m_Pixels = 0;

		delete l_Canvas;
	}else{
		// shouldn't happen because image_file::load 
		// should throw in case of an error.
		throw invalid_operation("an error occured while loading the image file.");
	}
}

canvas::canvas(){
	m_Pitch = 0;
	m_Width = 0;
	m_Height = 0;
	m_Locked = false;
	m_Pixels = 0;
}

canvas::canvas(int a_Width, int a_Height){
	m_Pitch  = next_power_of_two(a_Width);
	m_Width  = a_Width;
	m_Height = a_Height;
	m_Locked = false;

	m_Pixels = new pixel_t[m_Pitch * a_Height];
}

canvas::~canvas(){
	if(m_Pixels){
		delete[] m_Pixels;
		m_Pixels = 0;
	}
}

void canvas::set_buffer(pixel_t *a_Pixels){
	m_Pixels = a_Pixels;
}

void canvas::set_width(int a_Width){
	m_Width = a_Width;
}

void canvas::set_height(int a_Height){
	m_Height = a_Height;
}

void canvas::set_pitch(int a_Pitch){
	m_Pitch = a_Pitch;
}

void canvas::paint(int x, int y, pixel_t a_Color){
	if(x < 0 || y < 0) return;
    m_Pixels[y * m_Pitch + x]  = a_Color;
}

void canvas::clear(pixel_t a_Color){
	//+memset(m_Pixels, a_Color, m_Height * m_Pitch * sizeof(pixel_t));
	for(int y = 0; y < m_Height; y++)
		for(int x = 0; x < m_Width; x++)
			m_Pixels[y * m_Width + x] = a_Color;
}

color3 canvas::color_at(int x, int y){
    char l_Red, l_Green, l_Blue;

	pixel_t a_Color = m_Pixels[y * get_pitch() + x];

	// WHEN BROKEN, look at the paint() method for a reason

	l_Red   = static_cast<char>((a_Color & 0xFF0000) >> 16);
	l_Green = static_cast<char>((a_Color & 0x00FF00) >> 8);
	l_Blue  = static_cast<char>((a_Color & 0x0000FF));

	return math::color3(
         static_cast<float>(l_Red) / 255.f, 
         static_cast<float>(l_Green) / 255.f, 
         static_cast<float>(l_Blue) / 255.f
    );
}

void canvas::lock(){
    m_Locked = true;
}

void canvas::unlock(){
    m_Locked = false;
}

void canvas::copy_to(canvas *a_Other){
	// TODO: implement
}

namespace
{
	int sgn(int a) { return a >= 0 ? 1 : -1; }
}

void canvas::line(int ix1, int iy1, int ix2, int iy2, pixel_t color){
	int x = ix1;
	int y = iy1;
	int dx = abs(ix2 - ix1);
	int dy = abs(iy2 - iy1);
	int sx = sgn(ix2 - ix1);
	int sy = sgn(iy2 - iy1);

	int swapped = dy > dx;
	if (swapped) {
		int tmp = dy;
		dy = dx;
		dx = tmp;
	}

	int err = dy+dy-dx;
	int i = 0;
	if (swapped)
		while (i++<=dx)   {
			paint(x,y,color);
			if (err >= 0) {
				x += sx;
				err -= dx+dx;
			}
			y += sy;
			err += dy+dy;
		}
	else
		while (i++<=dx)   {
			paint(x,y,color);
			if (err >= 0) {
				y+=sy;
				err -= dx+dx;
			}
			x+=sx;
			err += dy+dy;
		}
}

pixel_t canvas::mix_color(pixel_t r, pixel_t g, pixel_t b) const{
	return ((pixel_t)r << 16) + ((pixel_t)g << 8) + (pixel_t)b;
	// THIS MIGHT BE THE REASON color_at is broken, if it's broken
	//char l_Temp[4] = {r, g, b, 0};
	//return *static_cast<pixel_t*>(l_Temp);
	//return (
}

pixel_t canvas::mix_color(const color3 &a_Color) const{
	math::color3 l_Temp = a_Color * 255.f;
	return mix_color(clamp(l_Temp.m_R), clamp(l_Temp.m_G), clamp(l_Temp.m_B));
}

pixel_t canvas::get_pixel(int x, int y){
    if(x < 0) x = abs(x);
    if(y < 0) y = abs(y);

    x %= m_Width;
    y %= m_Height;

	return m_Pixels[x + get_pitch() * y];
}

char canvas::clamp(const float a_Value) const{
    return static_cast<char>(std::max<float>(0, std::min<float>(a_Value, 0xFF)));
}

void canvas::circle(int a_X, int a_Y, int a_Radius, pixel_t a_Color){
    int l_Error = 1 - a_Radius;
    int l_ErrorX = 0;
    int l_ErrorY = -2 * a_Radius;
    int x = 0;
    int y = a_Radius;
    
    pixel_t l_Pixel = a_Color;
    
    while(x < y){
        if(l_Error >= 0){
            y--;
            l_ErrorY += 2;
            l_Error += l_ErrorY;
        }
        l_ErrorX += 2;
        l_Error += l_ErrorX + 1;
        
        paint(a_X + x, a_Y + y, l_Pixel);
        paint(a_X - x, a_Y + y, l_Pixel);
        paint(a_X + x, a_Y - y, l_Pixel);
        paint(a_X - x, a_Y - y, l_Pixel);
        
        paint(a_X + y, a_Y + x, l_Pixel);
        paint(a_X - y, a_Y + x, l_Pixel);
        paint(a_X + y, a_Y - x, l_Pixel);
        paint(a_X - y, a_Y - x, l_Pixel);
        x++;
    }
}

void canvas::store(const std::string &a_Filename){
	image_file_manager::get_instance()->store(a_Filename, this);
}

canvas *canvas::resize(int a_Width, int a_Height, scale_quality a_Quality){
	canvas *l_Canvas = new canvas(a_Width, a_Height);

	if(a_Quality == scale_linear){ 
		resize_to_linear(l_Canvas);
	}else if(a_Quality == scale_bilinear){
		resize_to_bilinear(l_Canvas);
	}else if(a_Quality == scale_bicubic){
		resize_to_bicubic(l_Canvas);
	}else{
		throw invalid_operation("Invalid scale quality specified");
	}

	return l_Canvas;
}

void canvas::resize_to_linear(canvas *a_Target){
	pixel_t *l_Dest = a_Target->get_buffer();
	pixel_t *l_Src  = get_buffer();

	for(int y = 0; y < get_height(); y++){
		for(int x = 0; x < get_width(); x++){
			l_Dest[(y / 2) * a_Target->get_width() + (x / 2)]
				= l_Src[y * get_width() + x];
		}
	}
}

void canvas::resize_to_bilinear(canvas *a_Target){
}

void canvas::resize_to_bicubic(canvas *a_Target){
}

//std::list<canvas*> canvas::generate_mip_maps(scale_quality a_Quality){
//	std::list<canvas*> l_MipMapLevels;
//
//	l_MipMapLevels.push_back(this);
//
//	int l_MaxLevel = (int)log(std::max((float)get_width(), (float)get_height()));
//
//	for(int i = 0; i < l_MaxLevel; i++){
//		canvas *l_SmallestLevel = l_MipMapLevels.back();
//		l_MipMapLevels.push_back(
//			l_SmallestLevel->resize(
//				l_SmallestLevel->get_width() / 2,
//				l_SmallestLevel->get_height() / 2,
//				a_Quality
//			)
//		);
//	}
//
//	return l_MipMapLevels;
//}


std::list<canvas*> canvas::generate_mip_maps(scale_quality a_Quality){
	int l_Width = m_Width;
	int l_Height = m_Height;

	std::list<canvas*> l_MipMapLevels;
	canvas *l_Src = this;

	while(l_Width || l_Height){
		l_MipMapLevels.push_back(l_Src);

		l_Width >>= 1;
		l_Height >>= 1;

		canvas *l_Dest			= new canvas(l_Width, l_Height);

		int l_Pitch				= l_Src->get_pitch();
		pixel_t *l_Buffer		= l_Src->get_buffer();

		int l_NewPitch			= l_Dest->get_pitch();
		pixel_t *l_NewBuffer	= l_Dest->get_buffer();


		for(int y = 0; y < l_Src->get_height(); y += 2){
			for(int x = 0; x < l_Src->get_width(); x += 2){
				pixel_t l_TopLeft     = l_Buffer[x + (y * l_Pitch)];
				pixel_t l_TopRight    = l_Buffer[(x + 1) + (y * l_Pitch)];
				pixel_t l_BottomLeft  = l_Buffer[x + ((y + 1) * l_Pitch)];
				pixel_t l_BottomRight = l_Buffer[(x + 1) + ((y + 1) * l_Pitch)];

				pixel_t l_Red = 
					  ((l_TopLeft     & 0xFF0000) >> 16) 
					+ ((l_TopRight    & 0xFF0000) >> 16)
					+ ((l_BottomLeft  & 0xFF0000) >> 16)
					+ ((l_BottomRight & 0xFF0000) >> 16);

				pixel_t l_Green =
					  ((l_TopLeft     & 0x00FF00) >> 8)
					+ ((l_TopRight    & 0x00FF00) >> 8)
					+ ((l_BottomLeft  & 0x00FF00) >> 8)
					+ ((l_BottomRight & 0x00FF00) >> 8);

				pixel_t l_Blue =
					  (l_TopLeft     & 0x0000FF)
					+ (l_TopRight    & 0x0000FF)
					+ (l_BottomLeft  & 0x0000FF)
					+ (l_BottomRight & 0x0000FF);

				l_NewBuffer[x >> 1] = 
					  ((l_Red >> 2) << 16) 
					+ ((l_Green >> 2) << 8) 
					+  (l_Blue >> 2);
			}

			l_NewBuffer += l_NewPitch;
		}

		l_Src = l_Dest;
	}

	return l_MipMapLevels;
}

void canvas::vflip(){
	pixel_t *tmp = new pixel_t[m_Pitch];
	for (int y = 0; y < m_Height/2; y++) {
        memcpy(tmp, &m_Pixels[y * m_Pitch], m_Pitch * sizeof(pixel_t));
        memcpy(&m_Pixels[y * m_Pitch], &m_Pixels[(m_Height - y - 1) * m_Pitch], m_Pitch * sizeof(pixel_t));
        memcpy(&m_Pixels[(m_Height - y - 1) * m_Pitch], tmp, m_Pitch * sizeof(pixel_t));
    }
	delete[] tmp;
}